import * as fs from 'fs';
import * as path from 'path';

interface JsonConfigData {
  [key: string]: any;
}

/**
 * JsonConfig 类用于管理 JSON 格式的配置文件。
 * 它提供读取、写入、更新和删除配置项的方法。
 */
class JsonConfig {
  private filePath: string; // 配置文件路径
  private data: JsonConfigData; // 存储解析后的配置数据

  /**
   * 构造函数，初始化配置管理器并加载或创建配置文件。
   * @param filePath - 配置文件的路径（相对或绝对路径）
   * @param defaultContent - 如果文件不存在，则使用此默认内容初始化文件，默认为空对象
   */
  constructor(filePath: string, defaultContent: string = '{}') {
    this.filePath = path.resolve(filePath);
    this.data = {};
    this.load(defaultContent); // 加载配置文件内容到内存中，如果文件不存在则初始化
  }

  /**
   * 私有方法，从文件加载配置数据到内存，或者用默认内容初始化。
   * @param defaultContent - 如果文件不存在，则使用此默认内容初始化文件
   */
  private load(defaultContent: string): void {
    try {
      const dir = path.dirname(this.filePath);

      // 确保所有必要的父级目录都存在
      if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
      }

      if (!fs.existsSync(this.filePath)) {
        // 如果文件不存在，则创建文件并用默认内容初始化
        fs.writeFileSync(this.filePath, defaultContent, 'utf8');
        this.data = JSON.parse(defaultContent);
      } else {
        const data = fs.readFileSync(this.filePath, 'utf8');
        this.data = JSON.parse(data);
      }
    } catch (err: unknown) {
      const error = err as Error;
      if (error instanceof SyntaxError) {
        console.error(`配置文件 ${this.filePath} 的 JSON 格式不正确: ${error.message}`);
        this.data = {}; // 初始化为空对象以避免程序崩溃
      } else {
        throw error; // 对于其他类型的错误，重新抛出
      }
    }
  }

  /**
   * 重新加载配置文件内容到内存中。
   */
  public reload(): void {
    this.load('{}'); // 使用空对象作为默认内容重新加载
  }

  /**
   * 获取整个配置对象的副本。
   * @returns 返回配置对象的一个深拷贝
   */
  public read(): JsonConfigData {
    return { ...this.data };
  }

  /**
   * 获取指定键对应的顶层值。
   * @param key - 要获取的配置项的键名
   * @returns 返回与键关联的值；如果键不存在则返回 null
   */
  public get(key: string): any | null {
    // 只获取给定键的一层子元素
    return this.data[key] !== undefined ? this.data[key] : null;
  }

  /**
   * 设置指定键的值，并保存到文件。
   * @param key - 要设置的配置项的键名
   * @param value - 键对应的新值
   */
  public set(key: string, value: any): void {
    this.data[key] = value;
    this.save(); // 将更改保存回文件
  }

  /**
   * 删除指定键及其对应的值，并保存到文件。
   * @param key - 要删除的配置项的键名
   */
  public delete(key: string): void {
    if (this.data.hasOwnProperty(key)) {
      delete this.data[key];
      this.save(); // 将更改保存回文件
    }
  }

  /**
   * 将当前内存中的配置数据保存到文件。
   */
  private save(): void {
    const dir = path.dirname(this.filePath);

    // 确保所有必要的父级目录都存在
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }

    fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), 'utf8');
  }

  /**
   * 如果配置中指定键不存在，则创建并初始化它的值，并保存更新到文件。
   * @param key - 要初始化的配置项的键名
   * @param defaultValue - 键对应的初始值
   */
  public init(key: string, defaultValue: any): void {
    if (!(key in this.data)) {
      this.set(key, defaultValue); // 如果键不存在，则设置它
    }
  }
}

export default JsonConfig;